//
// Created by LiuYou on 2021/5/9.
//

#include <cstdio>
#include <cstdlib>
#include <glob.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>

static int64_t mydu(const char* path);


static bool pathNoLoop(const char* path);

/**
 * 这个函数不能看 /etc/下的目录, 这个原因我不知道, 离谱。
 * 不过其它目录是可以的, 计算和 du命令的结果是相同的。
 */
int main(int argc, char** argv) {

    if (argc < 2) {
        std::fprintf(stderr, "Usage...");
        std::exit(1);
    }

    // 除以2的原因: block = 512 bytes。
    // 1KB 为 1024 bytes。
    std::printf("%ld\n", mydu(argv[1]) / 2);

    return 0;
}

/**
 * 这里是进行了递归优化的版本。
 *
 * 优化递归:
 * 将一些不放在栈中的对象变量都拿出去。
 * 原则:
 * 一个变量或对象的使用时单独在递归点之前/之后, 那么这个对象/变量可以被优化到静态存储区中去存放。
 *
 * 这里谈到了全局变量, 一般使用全局变量都会将它修饰为 static。
 * @param path
 * @return
 */
static int64_t mydu(const char* path) {
    // 基线条件: 非目录文件或者空目录
    // 判断path是目录还是非目录, 这就使用到了之前学习的函数 stat, 但是如果是符号链接的话不展开, 所以使用 lstat函数。
    // todo 注意: 设置为 static进行优化
    static struct stat fileStatus{};
    int ret = lstat(path, &fileStatus);
    if (ret < 0) {
        std::perror("lstat()");
        std::exit(1);
    }
    // todo 注意: 设置为 static进行优化
    static const int PATH_SIZE{1024};
    // todo 注意: 设置为 static进行优化
    static char nextPath[PATH_SIZE]{};
    std::memset(nextPath, '\0', PATH_SIZE);
    // 基线条件
    if (!S_ISDIR(fileStatus.st_mode)) {
        return fileStatus.st_blocks;
    } else {
        std::strncpy(nextPath, path, PATH_SIZE);
//        std::strncpy(nextPath, path, sizeof(nextPath));
//        std::strncat(nextPath, "/*", PATH_SIZE);
        std::strcat(nextPath, "/*");
        // 位图不设置(特殊要求没有), 出错路径不关心, 结果放到globResult中。
        // todo 注意: 这个不能放到静态区, 因为 globResult对象递归使用到了。
        glob_t globResult{};
        // 解析目录下非隐藏的文件
        ret = glob(nextPath, 0, NULL, &globResult);
        if (ret) {
            // 这个这个出错不会设置errno值, 那就直接打印信息即可。
//            std::fprintf(stderr, "Error code = %d\n", ret);
            std::fprintf(stderr, "Error code = %d, %s\n", ret, std::strerror(errno));
            std::exit(1);
        }
        std::strncpy(nextPath, path, PATH_SIZE);
//        std::strncat(nextPath, "/.*", PATH_SIZE);
        std::strcat(nextPath, "/.*");
        // 这里使用glob特殊操作, 追加到 globResult变量中。这样就把所有隐藏的和非隐藏的文件都放到了 globResult对象中。
        ret = glob(nextPath, GLOB_APPEND, NULL, &globResult);
        if (ret) {
            // 这个这个出错不会设置errno值, 那就直接打印信息即可。
//            std::fprintf(stderr, "Error code = %d\n", ret);
            std::fprintf(stderr, "Error code = %d, %s\n", ret, std::strerror(errno));
            std::exit(1);
        }
        // todo 注意: 这样 fileStatus对象就只在递归点之前使用了, 这样就可以优化了: 将 fileStatus放到静态区中去。
        // todo 注意: 这个不能放到静态区, 因为 sum变量递归使用到了。
        int64_t sum = fileStatus.st_blocks;
        for (int i = 0; i < globResult.gl_pathc; ++i) {
            // 递归这里注意！！！！
            if (pathNoLoop(globResult.gl_pathv[i])) {
                sum += mydu(globResult.gl_pathv[i]);
            }
        }

//        sum += fileStatus.st_blocks;

        globfree(&globResult);
        return sum;


        // 如果是目录文件的话
        // path/*
//        glob();
//        stat()
        // path/.*
//        glob();
//        stat()
        return 0;
    }
}

/**
 * 没有进行递归优化之前的版本。
 * @param path
 * @return
 */
/*static int64_t mydu(const char* path) {
    // 基线条件: 非目录文件或者空目录
    // 判断path是目录还是非目录, 这就使用到了之前学习的函数 stat, 但是如果是符号链接的话不展开, 所以使用 lstat函数。
    struct stat fileStatus{};
    int ret = lstat(path, &fileStatus);
    if (ret < 0) {
        std::perror("lstat()");
        std::exit(1);
    }
    const int PATH_SIZE{1024};
    char nextPath[PATH_SIZE]{};
    std::memset(nextPath, '\0', PATH_SIZE);
    // 基线条件
    if (!S_ISDIR(fileStatus.st_mode)) {
        return fileStatus.st_blocks;
    } else {
        std::strncpy(nextPath, path, PATH_SIZE);
//        std::strncpy(nextPath, path, sizeof(nextPath));
//        std::strncat(nextPath, "/*", PATH_SIZE);
        std::strcat(nextPath, "/*");
        // 位图不设置(特殊要求没有), 出错路径不关心, 结果放到globResult中。
        glob_t globResult{};
        // 解析目录下非隐藏的文件
        ret = glob(nextPath, 0, NULL, &globResult);
        if (ret) {
            // 这个这个出错不会设置errno值, 那就直接打印信息即可。
//            std::fprintf(stderr, "Error code = %d\n", ret);
            std::fprintf(stderr, "Error code = %d, %s\n", ret, std::strerror(errno));
            std::exit(1);
        }
        std::strncpy(nextPath, path, PATH_SIZE);
//        std::strncat(nextPath, "/.*", PATH_SIZE);
        std::strcat(nextPath, "/.*");
        // 这里使用glob特殊操作, 追加到 globResult变量中。这样就把所有隐藏的和非隐藏的文件都放到了 globResult对象中。
        ret = glob(nextPath, GLOB_APPEND, NULL, &globResult);
        if (ret) {
            // 这个这个出错不会设置errno值, 那就直接打印信息即可。
//            std::fprintf(stderr, "Error code = %d\n", ret);
            std::fprintf(stderr, "Error code = %d, %s\n", ret, std::strerror(errno));
            std::exit(1);
        }
        int64_t sum{};
        for (int i = 0; i < globResult.gl_pathc; ++i) {
            // 递归这里注意！！！！
            if (pathNoLoop(globResult.gl_pathv[i])) {
                sum += mydu(globResult.gl_pathv[i]);
            }
        }

        sum += fileStatus.st_blocks;

        globfree(&globResult);
        return sum;


        // 如果是目录文件的话
        // path/*
//        glob();
//        stat()
        // path/.*
//        glob();
//        stat()
        return 0;
    }
}*/

static bool pathNoLoop(const char* path) {
    // todo 注意: 补充函数, 比较实用
    const char* pos = strrchr(path, '/');
    if (pos == nullptr) {
        std::exit(1);
    }
    if (std::strcmp(pos + 1, ".") == 0 || std::strcmp(pos + 1, "..") == 0) {
        return false;
    }
    return true;
}
